//
// Created by qinshizhong on 6/30/20.
//

#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <asm/errno.h>
#include "cJSON.h"
#include "uartlink.h"
#include "zcb.h"
#include "list.h"
#include "zm32_ap.h"
#include "zm32.h"
#include "debug.h"
#include "unidev.h"
#include "utils.h"


struct dev_cb dev_cb;





static struct {
    pthread_mutex_t mutex;
    int num;
    struct attr_reg_map *arm;
} arms = {PTHREAD_MUTEX_INITIALIZER, 0, NULL};


struct rw_req {
    cJSON *req;
    unidev_report cb;
    struct list_head node;
};


static struct rw_async {
    struct utils_thread rw;
    pthread_mutex_t mutex;
    struct list_head head;
} rw_async;


static struct {
    const char *name;
    unsigned char rel;
} relationship[] = {
        {"eq",      UNIDEV_EQ},
        {"neq",     UNIDEV_NEQ},
        {"gt",      UNIDEV_GT},
        {"ge",      UNIDEV_GE},
        {"lt",      UNIDEV_LT},
        {"le",      UNIDEV_LE},
};



static void * rdwr_async_thread(void *par)
{
    utils_thread_ts *psThreadInfo = (struct utils_thread *) par;
    struct rw_req *req;
    char result[2048];

    while (utils_check_running(psThreadInfo))
    {
        pthread_mutex_lock(&rw_async.mutex);
        if (!list_empty(&rw_async.head))
        {
            req = list_first_entry(&rw_async.head, struct rw_req, node);
            list_del(&req->node);
            pthread_mutex_unlock(&rw_async.mutex);
            rdwr_sync(req->req, result);
            if (req->cb)
            {
                req->cb(result);
            }

            cJSON_Delete(req->req);
            req->req = NULL;
            req->cb = NULL;
            free(req);
            req = NULL;
            continue;
        }
        pthread_mutex_unlock(&rw_async.mutex);
        usleep(10000);
    }

    return par;
}


const struct regs * regs_lookup(const struct attr *attr)
{
    if (!attr)
        return NULL;

    for (int i = 0; i < arms.num; ++i) {
        if (arms.arm[i].attr.id == attr->id &&
            arms.arm[i].attr.channel == attr->channel)
            return &arms.arm[i].regs;
    }

    return NULL;
}


const struct attr * attr_lookup(const struct regs *regs, struct regs *next)
{
    if (!regs)
        return NULL;

    for (int i = 0; i < arms.num; ++i) {
        if (arms.arm[i].regs.start == regs->start &&
            arms.arm[i].regs.num <= regs->num) {
            if (next) {
                next->start = regs->start + arms.arm[i].regs.num;
                next->num = regs->num - arms.arm[i].regs.num;
            }
            return &arms.arm[i].attr;
        }
    }

    return NULL;
}

const struct attr_reg_map * attr_reg_map_lookup(const struct attr *attr,
                                                const struct regs *regs)
{
    struct attr_reg_map *arm = NULL;

    if (!attr && !regs)
        return NULL;

    pthread_mutex_lock(&arms.mutex);
    if (attr) {
        for (int i = 0; i < arms.num; ++i) {
            if (attr->id == arms.arm[i].attr.id &&
                attr->channel == arms.arm->attr.channel) {
                arm = arms.arm + i;
                break;
            }

        }
    } else {
        for (int i = 0; i < arms.num; ++i) {
            if (regs->start == arms.arm[i].regs.start &&
                regs->num >= arms.arm[i].regs.num) {
                arm = arms.arm + i;
                break;
            }
        }
    }
    pthread_mutex_unlock(&arms.mutex);

    return arm;
}


static int rw_async_init(void)
{
    INIT_LIST_HEAD(&rw_async.head);
    pthread_mutex_init(&rw_async.mutex, NULL);
    if (utils_thread_start(rdwr_async_thread, &rw_async.rw) != EN_UTILS_OK) {
        LOGE("utils_thread_start");
        return -1;
    }
    return 0;
}


static void rw_async_finit(void)
{
    struct rw_req *pos = NULL, *n = NULL;

    if (EN_UTILS_OK != utils_thread_stop(&rw_async.rw))
        LOGE("rdwr_async_thread stop error");
    pthread_mutex_destroy(&rw_async.mutex);
    list_for_each_entry_safe(pos, n, &rw_async.head, node) {
        list_del_init(&pos->node);
        free(pos->req);
        free(pos);
    }
}

static int attr_map_retrieve(const cJSON *attr_map)
{
    cJSON *e = NULL, *a = NULL, *it = NULL;
    int num = 0;
    int i = 0;
    struct attr_reg_map arm;
    struct attr_reg_map *new = NULL;

    a = cJSON_GetObjectItemCaseSensitive(attr_map, "attr map");
    if (!cJSON_IsArray(a))
        return UNIDEV_ERR_CNF;

    num = cJSON_GetArraySize(a);
    new = (struct attr_reg_map *)calloc(1, sizeof(*arms.arm)*num);
    if (!new)
        return UNIDEV_ERR_MEM;

    cJSON_ArrayForEach(e, a) {
        it = cJSON_GetObjectItemCaseSensitive(e, "attr id");
        if (!it || !cJSON_IsNumber(it))
            continue;
        arm.attr.id = it->valueint;

        it = cJSON_GetObjectItemCaseSensitive(e, "channel");
        if (!it || !cJSON_IsNumber(it))
            continue;
        arm.attr.channel = it->valueint;

        it = cJSON_GetObjectItemCaseSensitive(e, "type");
        if (!it || !cJSON_IsString(it))
            continue;
        strncpy(arm.attr.type, it->valuestring, sizeof(arm.attr.type));

        it = cJSON_GetObjectItemCaseSensitive(e, "reg start");
        if (!it || !cJSON_IsNumber(it))
            continue;
        arm.regs.start = it->valueint;

        it = cJSON_GetObjectItemCaseSensitive(e, "reg num");
        if (!it || !cJSON_IsNumber(it))
            continue;
        arm.regs.num = it->valueint;

        new[i++] = arm;
    }
    pthread_mutex_lock(&arms.mutex);
    arms.num = i;
    free(arms.arm);
    arms.arm = new;
    pthread_mutex_unlock(&arms.mutex);

    return UNIDEV_SUCCESS;
}


int init(dev_report attr, dev_report join, dev_report quit, void *par)
{
    int ret;
    teSL_Status stat;
    cJSON *root = (cJSON *)par;
    cJSON *cnf = NULL, *attr_map = NULL;
    char *dev;
    int baud;

    if (!par)
        return UNIDEV_ERR_NULL;

    cnf = cJSON_GetObjectItemCaseSensitive(root, "cnf");
    attr_map = cJSON_GetObjectItemCaseSensitive(root, "attr map");
    if (!cnf || !cJSON_IsObject(cnf) || !attr_map || !cJSON_IsObject(attr_map))
        return UNIDEV_ERR_CNF;

    dev = cJSON_GetObjectItemCaseSensitive(cnf, "interface")->valuestring;
    baud = cJSON_GetObjectItemCaseSensitive(cnf, "baud")->valueint;
    ret = model_update(attr_map);
    if (ret != UNIDEV_SUCCESS)
    {
        return ret;
    }

    dev_cb.attr = attr;
    dev_cb.join = join;
    dev_cb.quit = quit;
    ret = rw_async_init();
    if (ret != 0)
    {
        LOGE("rw_async_init failed, ret = %d", ret);
        return UNIDEV_ERR_LIB;
    }

    zm32_init();
    stat = eSL_Init(dev, baud);
    if (stat != E_SL_OK)
    {
        LOGE("eSL_Init failed. error = %d", stat);
        return UNIDEV_ERR_LIB;
    }
    zcb_init();

    ret = zcb_networking_start();
    if (ret != 0)
    {
        LOGE("zcb_networking_start failed. error = %d", ret);
        return UNIDEV_ERR_LIB;
    }

    ret = zcb_rxtx_mode_set();
    if (ret != 0)
    {
        LOGE("zcb_rxtx_mode_set failed. error = %d", ret);
        return UNIDEV_ERR_LIB;
    }

    return UNIDEV_SUCCESS;
}


int finit(void)
{
    zcb_destroy();
    eSL_Destroy();
    rw_async_finit();
    zm32_finit();
    memset(&dev_cb, 0, sizeof(dev_cb));
    if (arms.arm) {
        free(arms.arm);
        memset(&arms, 0, sizeof(arms));
    }

    return UNIDEV_SUCCESS;
}



static int relationship_parse(const char *str, unsigned char *rel)
{
    if (!str || !rel)
        return UNIDEV_ERR_NULL;

    for (int i = 0; i < sizeof(relationship)/sizeof(relationship[0]); ++i) {
        if (!strcmp(relationship[i].name, str)) {
            *rel = relationship[i].rel;
            return UNIDEV_SUCCESS;
        }
    }

    return UNIDEV_ERR_VALUE;
}

static int scene_transform(const cJSON *scene, char *out, unsigned int *len)
{
    cJSON *it = NULL, *trigger = NULL, *action = NULL, *relations = NULL, *e = NULL, *e0 = NULL, *attr = NULL;
    unsigned char sceneid, logic;
    struct attr attribute;
    unsigned short cond_reg[MAX_RULES], act_reg[MAX_ACTS];
    unsigned char rel[MAX_RULES];
    unsigned char rule = 0, act = 0;
    const struct attr_reg_map *arm = NULL;
    char cond_val[128], act_val[128];
    int cond_val_idx = 0, act_val_idx = 0;
    unsigned int k = 0;

    if (!scene || !out || !len)
        return UNIDEV_ERR_NULL;

    it = cJSON_GetObjectItemCaseSensitive(scene, "scene id");
    sceneid = it->valueint;

    trigger = cJSON_GetObjectItemCaseSensitive(scene, "triggers");
    if (!trigger || !cJSON_IsObject(trigger))
        return UNIDEV_ERR_VALUE;

    action = cJSON_GetObjectItemCaseSensitive(scene, "action");
    if (!action || !cJSON_IsArray(action))
        return UNIDEV_ERR_VALUE;

    it = cJSON_GetObjectItemCaseSensitive(trigger, "logic");
    if (!it || !cJSON_IsNumber(it))
        return UNIDEV_ERR_VALUE;
    logic = it->valueint;

    relations = cJSON_GetObjectItemCaseSensitive(trigger, "relations");
    if (!relations || !cJSON_IsArray(relations))
        return UNIDEV_ERR_VALUE;
    cJSON_ArrayForEach(e, relations) {
        it = cJSON_GetObjectItemCaseSensitive(e, "relation");
        if (!it || !cJSON_IsString(it))
            continue;
        if (relationship_parse(it->valuestring, rel+rule) != UNIDEV_SUCCESS)
            continue;
        attr = cJSON_GetObjectItemCaseSensitive(e, "attr");
        if (!attr || !cJSON_IsObject(attr))
            continue;
        it = cJSON_GetObjectItemCaseSensitive(attr, "attribute id");
        if (!it || !cJSON_IsNumber(it))
            continue;
        attribute.id  = it->valueint;
        it = cJSON_GetObjectItemCaseSensitive(attr, "channel");
        if (!it || !cJSON_IsNumber(it))
            continue;
        attribute.channel = it->valueint;
        arm = attr_reg_map_lookup(&attribute, NULL);
        if (!arm)
            continue;
        cond_reg[rule] = htons(arm->regs.start);
        it = cJSON_GetObjectItemCaseSensitive(attr, "value");
        if (!it || !cJSON_IsString(it))
            continue;
        if (strton(it->valuestring, arm, cond_val+cond_val_idx) != UNIDEV_SUCCESS)
            continue;
        cond_val_idx += arm->regs.num;

        if (++rule >= MAX_RULES)
            break;
    }
    if (rule == 0)
        return UNIDEV_ERR_VALUE;

    cJSON_ArrayForEach(e, action) {
        attr = cJSON_GetObjectItemCaseSensitive(e, "attributes");
        if (!attr || !cJSON_IsArray(attr))
            continue;
        cJSON_ArrayForEach(e0, attr) {
            it = cJSON_GetObjectItemCaseSensitive(e0, "attribute id");
            if (!it || !cJSON_IsNumber(it))
                continue;
            attribute.id = it->valueint;

            it = cJSON_GetObjectItemCaseSensitive(e0, "channel");
            if (!it || !cJSON_IsNumber(it))
                continue;
            attribute.channel = it->valueint;

            arm = attr_reg_map_lookup(&attribute, NULL);
            if (!arm)
                continue;
            act_reg[act] = htons(arm->regs.start);

            it = cJSON_GetObjectItemCaseSensitive(e0, "value");
            if (!it || !cJSON_IsString(it))
                continue;
            if (strton(it->valuestring, arm, act_val+act_val_idx) != UNIDEV_SUCCESS)
                continue;
            act_val_idx += arm->regs.num;

            if (++act >= MAX_ACTS)
                break;
        }
        if (act > 0)
            break;
    }
    if (act == 0)
        return UNIDEV_ERR_VALUE;


    out[k++] = sceneid;
    out[k++] = logic;
    out[k++] = rule;
    memcpy(out+k, cond_reg, rule*2);
    k += rule*2;
    memcpy(out+k, cond_val, cond_val_idx);
    k += cond_val_idx;
    memcpy(out+k, rel, rule);
    k += rule;
    out[k++] = act;
    memcpy(out+k, act_reg, act*2);
    k += act*2;
    memcpy(out+k, act_val, act_val_idx);
    k += act_val_idx;

    *len = k;

    return UNIDEV_SUCCESS;
}

static int wr(unsigned long long int mac, const cJSON *pdu, const struct attr_reg_map *arm)
{
    int ret;
    cJSON *it = NULL;
    char buf[255];
    unsigned int len = 0;
    int count = 0;

    if (!pdu || !arm)
        return UNIDEV_ERR_NULL;

    it = cJSON_GetObjectItemCaseSensitive(pdu, "value");
    if (!cJSON_IsString(it) && !cJSON_IsObject(it))
        return UNIDEV_ERR_VALUE;

    if (!strncmp(arm->attr.type, TYPE_JSON_NAME, strlen(TYPE_JSON_NAME))) {
        ret = scene_transform(it, buf, &len);
    } else {
        ret = strton(it->valuestring, arm, buf);
        len = arm->regs.num;
    }
    if (ret != UNIDEV_SUCCESS)
        return ret;
    do {
        ret = zm32_write(mac, arm->regs.start, len, buf);
        if (ret == 0)
            break;
        else
            ret = UNIDEV_ERR_LIB;
    } while (count++ < RW_RETRY_TIMES);
    if (ret != 0)
        LOGI("zm32_write failed");

    return ret;
}

static int rd(unsigned long long int mac, const struct attr_reg_map *arm, char *str)
{
    int ret;
    int count = 0;
    char buf[255];

    if (!arm || !str)
        return UNIDEV_ERR_NULL;

    do {
        memset(buf, 0, sizeof(buf));
        ret = zm32_read(mac, arm->regs.start, arm->regs.num, buf);
        if (ret == 0)
            break;
        else
            ret = UNIDEV_ERR_LIB;
    } while (count++ < RW_RETRY_TIMES);

    if (!ret)
        ret = ntostr(buf, arm, str);
    else
        LOGI("zm32_read failed");

    return ret;
}


int rdwr_sync(const cJSON *request, char *result)
{
    const cJSON *pdu = NULL;
    cJSON *dup = NULL;
    cJSON *it = NULL;
    struct attr attr;
    const struct attr_reg_map *arm = NULL;
    int op;
    const char *uuid = NULL;
    int ret;
    unsigned long long int mac = 0;
    char *r = NULL;

    pdu          = cJSON_GetObjectItemCaseSensitive(request, "pdu");
    attr.id      = cJSON_GetObjectItemCaseSensitive(pdu, "attr id")->valueint;
    op           = cJSON_GetObjectItemCaseSensitive(pdu, "operation")->valueint;
    attr.channel = cJSON_GetObjectItemCaseSensitive(pdu, "channel")->valueint;
    uuid         = cJSON_GetObjectItemCaseSensitive(pdu, "uuid")->valuestring;
    mac          = strtoull(uuid, NULL, 16);
    arm = attr_reg_map_lookup(&attr, NULL);
    if (!arm)
    {
        LOGI("attr %d not found", attr.id);
        return UNIDEV_ERR_ATTRID;
    }
    dup = cJSON_Duplicate(request, 1);
    if (!dup)
        return UNIDEV_ERR_MEM;
    if (op) {
        ret = wr(mac, pdu, arm);
    } else {
        char str[255];
        it = cJSON_GetObjectItemCaseSensitive(dup, "pdu");
        cJSON_DeleteItemFromObjectCaseSensitive(it, "value");
        ret = rd(mac, arm, str);
        if (ret == UNIDEV_SUCCESS) {
            if (!cJSON_AddStringToObject(it, "type", arm->attr.type)) {
                ret = UNIDEV_ERR_MEM;
                goto out;
            }
            if (!cJSON_AddStringToObject(it, "value", str)) {
                ret = UNIDEV_ERR_MEM;
                goto out;
            }
        }
    }

    it = cJSON_GetObjectItemCaseSensitive(dup, "ret");
    if (it)
        cJSON_SetNumberValue(it, ret);
    else
        cJSON_AddNumberToObject(dup, "ret", ret);
    r = cJSON_Print(dup);
    if (!r) {
        ret = UNIDEV_ERR_MEM;
        goto out;
    }
    strcpy(result, r);
    cJSON_free(r);
    r = NULL;

out:
    cJSON_Delete(dup);
    dup = NULL;

    return ret;
}


int rdwr_async(const cJSON *request, unidev_report cb)
{
    const cJSON *pdu = NULL;
    cJSON *dup = NULL;
    struct attr attr;
    const struct regs *regs = NULL;
    int ret = UNIDEV_SUCCESS;
    struct rw_req *req = NULL;

    pdu          = cJSON_GetObjectItemCaseSensitive(request, "pdu");
    attr.id      = cJSON_GetObjectItemCaseSensitive(pdu, "attr id")->valueint;
    attr.channel = cJSON_GetObjectItemCaseSensitive(pdu, "channel")->valueint;
    regs = regs_lookup(&attr);
    if (!regs)
        return UNIDEV_ERR_ATTRID;
    dup = cJSON_Duplicate(request, 1);
    if (!dup)
        return UNIDEV_ERR_MEM;

    req = (struct rw_req *)malloc(sizeof(*req));
    if (!req) {
        ret = UNIDEV_ERR_MEM;
        goto fail;
    }
    req->req = dup;
    req->cb = cb;
    pthread_mutex_lock(&rw_async.mutex);
    list_add_tail(&req->node, &rw_async.head);
    pthread_mutex_unlock(&rw_async.mutex);


    return UNIDEV_SUCCESS;

fail:
    cJSON_Delete(dup);
    dup = NULL;

    return ret;
}


int networking_start(unsigned short dur)
{
    return nwk_join_perm(dur);
}


int networking_stop(void)
{
    return nwk_join_perm(0);
}


int dev_rm(const cJSON *request, char *result)
{
    const cJSON *pdu = NULL;
    cJSON *dup = NULL;
    cJSON *it = NULL;
    const char *uuid = NULL;
    int ret;
    unsigned long long int mac = 0;
    char *r = NULL;

    pdu = cJSON_GetObjectItemCaseSensitive(request, "pdu");
    uuid = cJSON_GetObjectItemCaseSensitive(pdu, "uuid")->valuestring;
    mac = strtoll(uuid, NULL, 16);
    dup = cJSON_Duplicate(request, 1);
    if (!dup) {
        ret = UNIDEV_ERR_MEM;
        goto out;
    }
    ret = zm32_write(mac, 0x1009, 1, NULL);
    if (!ret)
        ret = UNIDEV_SUCCESS;
    else
        ret = UNIDEV_ERR_LIB;
    it = cJSON_GetObjectItemCaseSensitive(dup, "ret");
    cJSON_SetNumberValue(it, ret);
    r = cJSON_Print(dup);
    if (!r) {
        ret = UNIDEV_ERR_MEM;
        goto out;
    }
    strcpy(result, r);
    cJSON_free(r);
    r = NULL;

out:
    if (dup) {
        cJSON_Delete(dup);
        dup = NULL;
    }
    return ret;
}

int model_update(void *par)
{
    if (!par)
        return UNIDEV_ERR_NULL;

    return attr_map_retrieve(par);
}